#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
@File    :   smooth_preprocessor.py    
@Contact :   pengwei.sun@aihuishou.com
@License :   (C)Copyright aihuishou

@Modify Time      @Author       @Version    @Desciption
------------      -----------   --------    -----------
2020-03-13 14:55   pengwei.sun      1.0         None
'''

from src.utils.config import logger
import os
import sys

sys.path.append(os.getcwd())
import pandas as pd
import numpy as np

from sklearn.linear_model import LinearRegression
from sklearn.isotonic import IsotonicRegression
import datetime

import warnings

warnings.filterwarnings("once")

# ## 测试函数

class intelligent_data_smooth:
    def __init__(self, df, dfm):
        self.predict_data = df
        self.model_data = dfm
        self._OLS_EXCEPTION_FLAG_ = False
        self.SKU_ID_CHECK_LIST = []
        self.mdata_sku_list = dfm.product_sku_id.unique()
        self.iter=0

    def _cal_Performance(self, y_true, y_pred, name_space=['mae', 'mse', 'explained_variance_score', 'r2_score']):
        '''
        Input
        y_true：真实的数据值
        y_pred：回归模型预测的数据值
        explained_variance_score：解释回归模型的方差得分，其值取值范围是[0,1]，越接近于1说明自变量越能解释因变量
        的方差变化，值越小则说明效果越差。
        mean_absolute_error：平均绝对误差（Mean Absolute Error，MAE），用于评估预测结果和真实数据集的接近程度的程度
        ，其其值越小说明拟合效果越好。
        mean_squared_error：均方差（Mean squared error，MSE），该指标计算的是拟合数据和原始数据对应样本点的误差的
        平方和的均值，其值越小说明拟合效果越好。
        r2_score：判定系数，其含义是也是解释回归模型的方差得分，其值取值范围是[0,1]，越接近于1说明自变量越能解释因
        变量的方差变化，值越小则说明效果越差。

        Return
        Python Dictionary
        '''
        from sklearn.metrics import mean_absolute_error, mean_squared_error, explained_variance_score, r2_score
        model_metrics_name = {'mae': mean_absolute_error, 'mse': mean_squared_error,
                              'explained_variance_score': explained_variance_score, 'r2_score': r2_score}
        mertics_result = {}

        for name, func in model_metrics_name.items():
            if name in name_space:
                tmp_score = func(y_true, y_pred)
                mertics_result[name] = tmp_score

        return mertics_result

    def smooth_strategy(self, use_template=False, sw_balance=True, balance_rule='rule', N_Degree=10):
        '''
        预测分调整
        Input
        use_template: 是否使用templateid作为模型拟合主体
        '''
        df_data_all = self.predict_data.copy(deep=True)
        # df_data_all = df_data_all.loc[df_data_all.product_sku_id.isin([783656, 772602, 783632])]
        #         set_flag = [True if i in ABT_RY_sku_list else False for i in df_data_all.product_sku_id]
        df_data = df_data_all[df_data_all.lastprice > 0].copy(deep=True)

        # 数据转换
        df_data['uniform_frp'] = df_data.groupby(['product_sku_id'])['lastprice'].apply(
            lambda x: x / max(x))

        df_data['log_uniform_frp'] = [np.log(i) for i in df_data.uniform_frp]
        df_data['product_sku_id_tmp']=df_data['product_sku_id']
        # 特征准备
        x_name_list = ['rank']
        # for degree in range(N_Degree):
        #     col_name = 'level_regression_x_' + str(degree)
        #     # a=df_data.groupby('product_sku_id')['product_level_order_rank']
        #     # for name,group in df_data.groupby('product_sku_id'):
        #     #     group[col_name]=group.groupby('product_sku_id')['product_level_order_rank'].agg('rank')
        #     # df_data[col_name] = (df_data.groupby('product_sku_id')['product_level_order_rank'].agg(
        #     #     'rank') - 1) ** degree
        #     df_data[col_name]=df_data['rank']**degree
        #     x_name_list.append(col_name)

        # if sw_balance:
        #     sample_w = self._get_sample_weight(criterion=balance_rule)
        # else:
        #     sample_w = {}
        sample_w = {}
        #         print(df_data.shape)
        if use_template:
            # 按templateid进行拟合 (接口还没有测试)
            temp_ypred = df_data.groupby('product_level_template_id').apply(
                lambda x: self._OLS_fit_func(x, x_name_list, sample_w, sample_balance=sw_balance))
        else:
            # 按sku进行拟合
            temp_ypred = df_data.groupby('product_sku_id').apply(
                lambda x: self._OLS_fit_func(x, x_name_list, sample_w, sample_balance=sw_balance))

        if self._OLS_EXCEPTION_FLAG_:
            print('OLS Function Fit Error, Check SKU List: %s' % (str(self.SKU_ID_CHECK_LIST)))

        temp_ypred = temp_ypred.reset_index()
        temp_ypred.columns = ['product_sku_id', 'id', 'y_pred','diff']
        temp_ypred = temp_ypred.set_index('id')
        df_data = df_data.merge(temp_ypred[['y_pred']], left_index=True, right_index=True)

        df_data_all = df_data_all.merge(df_data[['product_sku_id', 'product_level_id', 'y_pred']],
                                        left_on=['product_sku_id', 'product_level_id'],
                                        right_on=['product_sku_id', 'product_level_id'],
                                        how='right')

        df_data_all['y_pred'] = np.round(df_data_all.y_pred.fillna(0)).astype(int)

        #         print(df_data_all[df_data_all.product_sku_id == 791282][['product_level_order_rank','lastprice','y_pred']])

        #         df_data_all['frp_new'] = np.round(df_data_all[['lastprice','y_pred']].apply(lambda x:
        #                                  x.lastprice if x.y_pred == -1 else x.y_pred, axis=1)).astype(int)
        #         df_data_all['ppp_new'] = np.round(df_data_all[['lastprice','y_pred']].apply(lambda x:
        #                                  x.lastprice if x.y_pred != -1 else -1, axis=1)).astype(int)

        self.predict_data = df_data_all

        logger.info('lastprice reverse rate = {}'.format(self._get_reverse_rate(price_name='lastprice')))

        logger.info('y_pred reverse rate = {}'.format(self._get_reverse_rate(price_name='y_pred')))

        while self.predict_data[self.predict_data.y_pred > self.predict_data.rolling_min].shape[0] > 0:
            self.predict_data['y_pred'] = [self.predict_data.rolling_min.iloc[i] * 0.997
                                           if self.predict_data.y_pred.iloc[i] > self.predict_data.rolling_min.iloc[i]
                                           else self.predict_data.y_pred.iloc[i]
                                           for i in range(len(self.predict_data))]

            logger.info('the total reverse size={}'.format(self.predict_data[self.predict_data.y_pred > self.predict_data.rolling_min].shape[0]))
        logger.info('process y_pred reverse rate = {}'.format(self._get_reverse_rate(price_name='y_pred')))

    def _get_sample_weight(self, criterion):
        '''
        Input
        criterion: 'stat' 统计变量得出各level权重
                   'rule' 经验权重
        Return
        Dictionary
        '''
        sample_weight_dict = {}
        if criterion == 'rule':
            sample_weight_dict = {'S': 100, 'A+': 98, 'A1': 96, 'A': 94, 'A2': 92, 'B+1': 90, 'B+': 88, 'B+2': 86,
                                  'B1': 84, 'B': 82, 'B2': 80, 'C+1': 78, 'C+': 76, 'C+2': 74, 'C1': 72, 'C': 70,
                                  'C2': 68, 'D+1': 66,
                                  'D+': 64, 'D+2': 62, 'D1': 60, 'D': 58, 'D2': 56, 'D3': 54, 'E+1': 52, 'E+': 50,
                                  'E+2': 48, 'E1': 46,
                                  'E': 44, 'E2': 42, 'F+': 40, 'F': 38, 'G+': 36, 'G1': 34, 'G': 32, 'G2': 30, 'H+': 28,
                                  'H1': 26,
                                  'H': 24, 'H2': 22, 'I+': 20, 'I1': 18, 'I': 16, 'I2': 14, 'J+': 12, 'J': 10, 'K+': 8,
                                  'K': 6}

        elif criterion == 'stat':
            # 拓展transform，通过大类class进行定义，然后transform回小类别权重
            sample_weight_dict = self.model_data.groupby('product_level_name')['product_no'].agg('count').to_dict()

        else:
            print('critical error: criterion rule is not defined')
        return sample_weight_dict

    # 模型训练预测
    def _OLS_fit_func(self, df_look_tmp, x_columns, sample_weight_dict, intercept=False, sample_balance=True,
                      balance_column='product_level_name', y_column='lastprice',y_replace='saleprice'):
        '''
        Input
        df_look: Dataframe
        intercept: bool
        sample_balance: bool
        balance_criterion: 'stat' 统计变量得出各level权重
                           'rule' 经验权重
        balance_column: string
        x_columns: list
        y_column: string

        Return
        Series exp_ypred
        '''
        #         print(df_look.product_sku_id.tolist()[0])
        try:
            df_look=df_look_tmp.copy(deep=True)
            sku_no = df_look.product_sku_id_tmp.unique()[0]
            self.iter+=1
            logger.info('iter={},_OLS_fit_func sku_no={}'.format(self.iter,sku_no))
            # if sku_no in self.mdata_sku_list:
            #     amount_dict = self.model_data[self.model_data.product_sku_id == sku_no][
            #         ['sku_plus_level', 'amount']].set_index('sku_plus_level').to_dict(orient='dict')['amount']
            #     sum_adv = sum(list(amount_dict.values()))
            #     if sum_adv > 0:
            #         for i in amount_dict.keys():
            #             sample_weight_dict[i[-3:]] = 100 + amount_dict[i] * 10 / sum_adv
            OLS_model = IsotonicRegression(increasing=False)

            # 可以考虑通过self.model_data进行weight dict的设置
            # if sample_balance:
            #     sw = [sample_weight_dict[i] for i in df_look[balance_column]]
            # else:
            #     sw = [1 for i in df_look[balance_column]]
            # model fit
            # df_look.sort_values('product_level_order_rank',inplace=True)
            df_look.sort_values(by=['product_level_order_rank','rank',],inplace=True)
            df_look['saleprice']=df_look['saleprice'].fillna(-1)

            indexlist=df_look.index.tolist()
            for i in range(1,df_look.shape[0]):
                if df_look.loc[indexlist[i],y_column]>=df_look.loc[indexlist[i-1],y_column]  \
                        and df_look.loc[indexlist[i-1],'product_level_id']>420 \
                        and df_look.loc[indexlist[i],'saleprice']>0\
                        and (df_look.loc[indexlist[i],'qty']>0 or abs(df_look.loc[indexlist[i],y_column]/df_look.loc[indexlist[i-1],y_column])>1.1):
                    df_look.loc[indexlist[i],y_column]=df_look.loc[indexlist[i],y_replace]
             #s,a,b,c出现异常高的需要单独处理  skuid=783656    5800->7400->
            for i in range(1,df_look.shape[0]):
                if df_look.loc[indexlist[i],y_column]>=df_look.loc[indexlist[i-1],y_column]  and df_look.loc[indexlist[i-1],'product_level_id']<=420 \
                        and df_look.loc[indexlist[i],'saleprice']>0\
                        and (abs(df_look.loc[indexlist[i],y_column]/df_look.loc[indexlist[i],'saleprice'])>1.1):
                    df_look.loc[indexlist[i],y_column]=df_look.loc[indexlist[i],'saleprice']
            #重新检测一遍倒挂
            for i in range(1,df_look.shape[0]):
                if df_look.loc[indexlist[i],y_column]>=df_look.loc[indexlist[i-1],y_column]  and df_look.loc[indexlist[i-1],'product_level_id']>420 \
                        and df_look.loc[indexlist[i],'saleprice']>0\
                        and (df_look.loc[indexlist[i],'qty']>0 or abs(df_look.loc[indexlist[i],y_column]/df_look.loc[indexlist[i-1],y_column])>1.1):
                    df_look.loc[indexlist[i],y_column]=df_look.loc[indexlist[i],y_replace]

            df_look['rerank']=range(0,df_look.shape[0])#jg_basepricedatacategoryphone
            df_look['weight']=df_look['qty']+1
            OLS_model.fit(df_look['rerank'], df_look[y_column], sample_weight=df_look['weight'].values)
            # model param
            df_look['salepricetmp']=df_look['saleprice']
            df_look['product_level_name_tmp1']=df_look['product_level_name']
            # df_look['exp_ypred'] = np.exp(OLS_model.predict(df_look[x_columns])) * df_look.lastprice.max()
            df_look['exp_ypred'] = OLS_model.predict(df_look['rerank'])
            indexlist=df_look.index.tolist()
            for i in range(1,df_look.shape[0]):
                if df_look.loc[indexlist[i],'exp_ypred']>=df_look.loc[indexlist[i-1],'exp_ypred']:
                    df_look.loc[indexlist[i],'exp_ypred']=df_look.loc[indexlist[i-1],'exp_ypred'] * 0.997

            df_look['diff'] = df_look['lastprice'] - df_look['exp_ypred']
        except Exception as e:
            logger.info('EXCEPTION OCCURS:{}'.format(e))

            df_look['exp_ypred'] = df_look.lastprice
            self.SKU_ID_CHECK_LIST.append(df_look.product_sku_id.unique()[0])
            self._OLS_EXCEPTION_FLAG_ = True

        # result show
        #         print(df_look['exp_ypred'])
        #         aaa
        return df_look[['exp_ypred','diff']]

    def _get_reverse_rate(self, price_name='forecast_reference_price'):
        '''
        Input:
        price_name: string

        Return:k
        reverse_rate: float
        '''
        self.predict_data.lastprice = pd.to_numeric(self.predict_data.lastprice, errors='ignore')
        self.predict_data = self.predict_data.sort_values('product_level_order_rank', ascending=True)
        self.predict_data['rolling_min'] = self.predict_data.groupby('product_sku_id')[price_name].agg('cummin')
        over_all_rr = len(
            self.predict_data[self.predict_data[price_name] > (self.predict_data.rolling_min)]) / len(
            self.predict_data)

        return over_all_rr


